//-------------------------------------------------------------------------------
//	file spr-01.cpp
//	brief
//		spr-01.cpp: Read and display Shruthi patch information
//
//	$Id: spr-01.cpp 259 2014-04-07 08:57:24Z ac.verbeck@gmail.com $
//------------------------------------------------------------------------------
//-------------------------------------------------------------------------------
//	Copyright and Disclaimer Notice
//
//	Copyright (c) 2013-2014 A.C. Verbeck
//
//	This file is part of Shruthi Patch Reader (SPR)
//
//	Shruthi Patch Reader (SPR) is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	Shruthi Patch Reader (SPR) is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with Shruthi Patch Reader (SPR).
//	If not, see <http://www.gnu.org/licenses/>.
//
//-------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//	Environment
//------------------------------------------------------------------------------

#include <ctype.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <stdint.h>																//	Standard int types
#include <popt.h>

#include "p_mem.h"
#include "debug.h"
#include "patch.h"

#include "bi.h"

//------------------------------------------------------------------------------
// Local defines
//------------------------------------------------------------------------------
#define OPT_OPERATION		0x0001
#define OPT_PATCH			0x0002
#define OPT_ABOUT			0x0100
#define OPT_VERBOSE			0x0200

#define MAX_FILENAME_SZ		128
#define WORK_BUFFER_SZ		512

//------------------------------------------------------------------------------
// Local data (parameters / popt)
//------------------------------------------------------------------------------
static const char*		operation;												//	operation to be run (used in popt)
static const char*		file_name	= "patch";									//	Filename: Input (used in popt)
static poptContext		optCon;

//------------------------------------------------------------------------------
// Local data (operator processing)
//------------------------------------------------------------------------------
typedef struct {
	const char*		op;
	const char*		file_name;
	int				r_value;
} OPERATOR_PARMS;

typedef void (*OPERATOR_FN)(OPERATOR_PARMS* fm);

typedef struct {
	const char*		op;
	OPERATOR_FN		fn;
} OPERATOR_TAB;

static void op_patch_read(OPERATOR_PARMS* fm);
static void op_voytek(OPERATOR_PARMS* fm);

static OPERATOR_TAB otab[] = {
	{	"p-read",		op_patch_read		},									//	patch read				0
	{	"voytek",		op_voytek			}									//	Internal test operator	1
};
static const int otab_ct = sizeof(otab)/sizeof(otab[0]);

//------------------------------------------------------------------------------
// Main function
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
	uint32_t	opt=0;
	int			idx=0;
	bool		hit=false;

	OPERATOR_PARMS p1;

	struct poptOption optionsTable[] = {
		{ "op",		'o', POPT_ARG_STRING, &operation,	'o', "operation: see man page for details",		NULL	},
		{ "patch",	'p', POPT_ARG_STRING, &file_name,	'p', "input patch (default: patch.syx)",		NULL	},
		{ "about",	'a', POPT_ARG_NONE,	  NULL,			'a', "display information about the app",		NULL	},
		{ "verbose",'v', POPT_ARG_NONE,	  NULL,			'v', "enable verbose mode",						NULL	},
		POPT_AUTOHELP
		POPT_TABLEEND
	};

//------------------------------------------------------------------------------
//	No buffering -- for Eclipse
//------------------------------------------------------------------------------
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);

//------------------------------------------------------------------------------
//	Process arguments
//------------------------------------------------------------------------------
	optCon = poptGetContext(NULL, argc, (const char **)argv, optionsTable, 0);
	poptSetOtherOptionHelp(optCon, "[OPTIONS]*");
	if (argc == 1) {
		poptPrintHelp(optCon, stdout, 0);
		exit(1);
	}

	while ((idx = poptGetNextOpt(optCon)) >= 0) {
		switch (idx) {
		case 'o':
			opt |= OPT_OPERATION;
			break;
		case 'p':
			opt |= OPT_PATCH;
			break;
		case 'a':
			opt |= OPT_ABOUT;
			break;
		case 'v':
			opt |= OPT_VERBOSE;
			break;
		}
	}
	if (idx < -1) {
		fprintf(stderr, "%s: %s\n",
		        poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
		        poptStrerror(idx));
		exit(opt);
	}
	if (opt & OPT_ABOUT) {
		bldinfoPrintBldInfo();
		exit(0);
	}
	if ((opt & OPT_OPERATION) == 0) {
		printf("Operation (op) required!\n");
		exit(0);
	}

//------------------------------------------------------------------------------
//	Execute the operation (if we can find it)
//------------------------------------------------------------------------------
	p1.op 			= operation;
	p1.file_name	= file_name;
	p1.r_value		= 0;

//------------------------------------------------------------------------------
//	Execute the operation (if we can find it)
//------------------------------------------------------------------------------
	for (char *p = (char*)operation; *p; ++p) *p = tolower(*p);					//	Convert the string to lower case
	for (idx=0; idx<otab_ct; idx++) {											//	Go through the OTAB table
		if (strcmp(operation, otab[idx].op) == 0) {								//	If we have a match...
			(otab[idx].fn)(&p1);												//	Call the function
			hit = true;															//	Indicate that we executed the function
		}
	}

//------------------------------------------------------------------------------
//	If no function was executed, then note the fact and exit
//	If a function was executed, return the value from the function
//------------------------------------------------------------------------------
	if (hit == false)
		printf("Did not find operation: %s\n",operation);

	return (hit == true) ? p1.r_value : 0;
}


//------------------------------------------------------------------------------
//	Function:
//		op_patch_read()
//
//	Description:
//		Reads the input patch and displays all the values
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//
//------------------------------------------------------------------------------
static const uint8_t sysex_start		= 0xf0;									// SysEx message start byte
static const uint8_t sysex_end			= 0xf7;									// SysEx message end byte
static const uint8_t sysex_hdr[]		= {0x00, 0x21, 0x02, 0x00, 0x02};		// MI manufacturer ID / Shruthi Product ID
static const uint32_t sysex_hdr_ct		= 5;

static void op_patch_read(OPERATOR_PARMS* fm)
{
	char		file_name[MAX_FILENAME_SZ];										// File name buffer
	uint8_t		work_buff[WORK_BUFFER_SZ];										// Work buffer
	uint8_t		out_buff[WORK_BUFFER_SZ];										// output buffer
	FILE*		pf_file;														// Input file pointer

	size_t		sysex_ct;
	uint32_t	patch_sz;
	uint8_t		cs=0;

//
//	Read the patch
//
	strcpy(file_name, fm->file_name);											// Copy in file name
	pf_file = fopen(file_name, "rb");											// Input file pointer
	if (pf_file == NULL) {														// If no file
		printf("Could not open %s\n", fm->file_name);							// Display it
		exit(1);																// Exit
	}
	memset(out_buff, 0, WORK_BUFFER_SZ);										// Clear the output buffer
	memset(work_buff, 0, WORK_BUFFER_SZ);										// Clear the input buffer
	sysex_ct = fread(work_buff, sizeof(uint8_t), WORK_BUFFER_SZ, pf_file);		// Read the patch
	fclose(pf_file);															// Close the file

//
//	Validate the SysEx:
//	- verify it starts with 0xf0 and ends with 0xf7
//	- verify the manufacturer / model id
//
	if (work_buff[0] != sysex_start) {											//	Test for a good start
		printf("Bad sysex start\n");											//	...note problem, and
		exit(1);																//	...exit
	}
	if (work_buff[sysex_ct-1] != sysex_end) {									//	Test for a good end
		printf("Bad sysex end\n");           									//	...note problem, and
		exit(1);                             									//	...exit
	}
	for (uint32_t i=0; i<sysex_hdr_ct; i++) {									//	Loop through SysEx header
		if (work_buff[i+1] != sysex_hdr[i]) {									//	Test for a good header
			printf("Bad sysex header\n");                                       //	...note problem, and
			exit(1);                                                            //	...exit
		}
	}

//
//	Convert the SysEx
//	- convert from 4-bits/byte to 8-bits/byte
//	- verify the checksum
//
	for (uint32_t i=0,j=0; i<sysex_ct-9; i+=2,j++) {							//	Loop through payload bytes
		out_buff[j] = work_buff[i+8]<<4;										//	Get upper byte
		out_buff[j] |= work_buff[i+9];											//	Get lower byte
	}
	patch_sz = (sysex_ct-11)/2;													//	Calculate patch size
	for (uint32_t i=0; i<patch_sz; i++)											//	Loop through patch bytes
		cs += out_buff[i];														//	Calculate patch checksum
	if (cs != out_buff[patch_sz]) {												//	If it doesn't match...
		printf("Bad check sum: %02x %02x\n", cs, out_buff[patch_sz]);			//	...note it and
		exit(1);																//	...exit
	}

//
//	Display the patch data
//
	printf("Patch dump (HEX / ANSII):\n");
	p_mem(out_buff, patch_sz);													//	Display the verified patch

	printf("\nPatch dump (parameter based):\n");
	patch_dump((Patch*)out_buff);
}

//------------------------------------------------------------------------------
//	Function:
//		op_void()
//
//	Description:
//		Internal function that displays the incoming OPERATOR_PARMS structure.
//		This is a model for how to write operator functions.
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//		Returns 7
//		to the system via the BASH $? variable.
//		Or the  %errorlevel% variable in windows command shell.
//
//		This function uses the mnemonic "voytek".  This is a character from
//		the William Gibson novel "Pattern Recognition".
//
//------------------------------------------------------------------------------
static void op_voytek(OPERATOR_PARMS* fm)
{
	fm->r_value = 7;

	printf("hit: voytek\n");
	printf("     operation: %s\n", fm->op);
	printf("      filename: %s\n", fm->file_name);
	printf("  return value: %d\n", fm-> r_value);
}

//
//	End: spr-01.cpp
//
